package perform.mappings;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import perform.annotations.Column;
import perform.annotations.ID;
import perform.annotations.ManyToMany;
import perform.annotations.ManyToOne;
import perform.annotations.OneToMany;
import perform.annotations.OneToOne;
import perform.annotations.Table;
import perform.database.ColumnProcessor;
import perform.database.SimpleColumnProcessor;
import perform.type.Type;

public class ClassMappings {
	
	/* Unique parts */
	private static volatile Map<Class<? extends Type>, String> tables = 
			new HashMap<Class<? extends Type>, String>();
	private static volatile Map<Class<? extends Type>, String> idColumns = 
			new HashMap<Class<? extends Type>, String>();
	private static volatile Map<Class<? extends Type>, Field> idFields = 
			new HashMap<Class<? extends Type>, Field>();
	private static volatile Map<Class<? extends Type>, ColumnProcessor> idColumnProcessors = 
			new HashMap<Class<? extends Type>, ColumnProcessor>();
	/* Columns */
	private static volatile Map<Class<? extends Type>, Map<Field, String>> classColumns = 
			new HashMap<Class<? extends Type>, Map<Field,String>>();
	private static volatile Map<Class<? extends Type>, Map<Field, String>> classM2Os = 
			new HashMap<Class<? extends Type>, Map<Field,String>>();
	private static volatile Map<Class<? extends Type>, Map<Field, String>> classO2Os = 
			new HashMap<Class<? extends Type>, Map<Field,String>>();
	private static volatile Map<Class<? extends Type>, Map<Field, String>> classAllColumns = 
			new HashMap<Class<? extends Type>, Map<Field,String>>();
	/* Relations */
	private static volatile Map<Class<? extends Type>, Map<Field, Field>> classO2Ms = 
			new HashMap<Class<? extends Type>, Map<Field, Field>>();
	private static volatile Map<Class<? extends Type>, Map<Field, ManyToManyMapping>> classM2Ms = 
			new HashMap<Class<? extends Type>, Map<Field,ManyToManyMapping>>();
	private static volatile Map<String, ManyToManyMapping> manyToManies = 
			new HashMap<String, ManyToManyMapping>();
	
	public static void addClass(Class<? extends Type> clazz){
		String tableName = clazz.getAnnotation(Table.class).name();
		if(tableName.equals("")){
			tableName = clazz.getSimpleName().toLowerCase();
		}
		tables.put(clazz, tableName);
		Field[] fields = clazz.getDeclaredFields();
		/* */
		Map<Field, String> columns = new HashMap<Field, String>();
		Map<Field, String> m2os = new HashMap<Field, String>();
		Map<Field, String> o2os = new HashMap<Field, String>();
		Map<Field, String> allColumns = new HashMap<Field, String>();
		Map<Field, Field> o2ms = new HashMap<Field, Field>();
		/* */
		for (int i = 0; i < fields.length; i++) {
			Field field = fields[i];
			field.setAccessible(true);
			if(field.isAnnotationPresent(Column.class)){
				String columnName = field.getAnnotation(Column.class).name();
				if(columnName.equals("")){
					columnName = field.getName();
				}
				columns.put(field, columnName);
				if(field.isAnnotationPresent(ID.class)){
					idColumns.put(clazz, columnName);
					idFields.put(clazz, field);
					idColumnProcessors.put(clazz, new SimpleColumnProcessor(field, columnName));
				}
			} else if(field.isAnnotationPresent(ManyToOne.class)){
				String columnName = field.getAnnotation(ManyToOne.class).name();
				if(columnName.equals("")){
					columnName = field.getName();
				}
				m2os.put(field, columnName);
			} else if(field.isAnnotationPresent(OneToOne.class)){
				String columnName = field.getAnnotation(OneToOne.class).name();
				if(columnName.equals("")){
					columnName = field.getName();
				}
				o2os.put(field, columnName);
			} else if(field.isAnnotationPresent(OneToMany.class)){
				try {
					OneToMany o2m = field.getAnnotation(OneToMany.class);
					Field inverse = o2m.target().getDeclaredField(o2m.inverseField());
					inverse.setAccessible(true);
					o2ms.put(field, inverse);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			} else if(field.isAnnotationPresent(ManyToMany.class)){
				ManyToMany m2m = field.getAnnotation(ManyToMany.class);
				ManyToManyMapping mapping = manyToManies.get(m2m.table());
				if(mapping == null){
					mapping = new ManyToManyMapping(m2m.table());
					if(!m2m.column().equals("")){
						mapping.setColumn1(m2m.column());
					}
					if(!m2m.inverseColumn().equals("")){
						mapping.setColumn2(m2m.inverseColumn());
					}
					mapping.setField1(field);
					mapping.setField2(idFields.get(m2m.target()));
					mapping.setClass1(clazz);
					mapping.setClass2(m2m.target());
				} else {
					if(!m2m.inverseColumn().equals("")){
						mapping.setColumn1(m2m.inverseColumn());
					}
					if(!m2m.column().equals("")){
						mapping.setColumn2(m2m.column());
					}
					mapping.setField2(field);
					mapping.setClass2(clazz);
				}
				manyToManies.put(m2m.table(), mapping);
			}
		}
		if(!columns.isEmpty()){
			classColumns.put(clazz, columns);
			allColumns.putAll(columns);
		}
		if(!m2os.isEmpty()){
			classM2Os.put(clazz, m2os);
			allColumns.putAll(m2os);
		} else {
			classM2Os.put(clazz, null);
		}
		if(!o2os.isEmpty()){
			classO2Os.put(clazz, o2os);
			allColumns.putAll(o2os);
		} else {
			classO2Os.put(clazz, null);
		}
		if(!allColumns.isEmpty()){
			classAllColumns.put(clazz, allColumns);
		}
		if(!o2ms.isEmpty()){
			classO2Ms.put(clazz, o2ms);
		} else {
			classO2Ms.put(clazz, null);
		} 
	}
	public static void finalizeMappings(){
		if(manyToManies != null){
			Iterator<String> m2mNames = manyToManies.keySet().iterator();
			while (m2mNames.hasNext()) {
				String m2mName = m2mNames.next();
				ManyToManyMapping mapping = manyToManies.get(m2mName);
				mapping.setIdColumn1(idColumnProcessors.get(mapping.getClass1()));
				mapping.setIdColumn2(idColumnProcessors.get(mapping.getClass2()));
				if(mapping.getColumn1() == null || mapping.getColumn1().equals("")){
					mapping.setColumn1(mapping.getField1().getName() + "_" + mapping.getIdColumn2().getName());
				}
				if(mapping.getColumn2() == null || mapping.getColumn2().equals("")){
					mapping.setColumn2(mapping.getField2().getName() + "_" + mapping.getIdColumn1().getName());
				}
				mapping.buildQueries();
				manyToManies.put(m2mName, mapping);
			}
			Iterator<Class<? extends Type>> classes = tables.keySet().iterator();
			while(classes.hasNext()){
				Class<? extends Type> clazz = classes.next();
				Map<Field, ManyToManyMapping> m2ms = new HashMap<Field, ManyToManyMapping>();
				Iterator<String> names = manyToManies.keySet().iterator();
				while(names.hasNext()){
					String name = names.next();
					ManyToManyMapping mapping = manyToManies.get(name);
					if(mapping.getClass1().equals(clazz)){
						m2ms.put(mapping.getField1(), mapping);
					} else if(mapping.getClass2().equals(clazz)){
						m2ms.put(mapping.getField2(), mapping);
					}
				}
				if(!m2ms.isEmpty()){
					classM2Ms.put(clazz, m2ms);
				}
			}
		}
	}
	
	public static String getTable(Class<? extends Type> clazz){
		return tables.get(clazz);
	}
	public static String getIDColumn(Class<? extends Type> clazz){
		return idColumns.get(clazz);
	}
	public static Field getIDField(Class<? extends Type> clazz){
		return idFields.get(clazz);
	}
	public static ColumnProcessor getIDColumnProcessor(Class<? extends Type> clazz){
		return idColumnProcessors.get(clazz);
	}
	public static Map<Field, String> getColumns(Class<? extends Type> clazz){
		return classColumns.get(clazz);
	}
	public static Map<Field, String> getManyToOnes(Class<? extends Type> clazz){
		return classM2Os.get(clazz);
	}
	public static Map<Field, String> getOneToOnes(Class<? extends Type> clazz){
		return classO2Os.get(clazz);
	}
	public static Map<Field, String> getAllColumns(Class<? extends Type> clazz){
		return classAllColumns.get(clazz);
	}
	public static Map<Field, Field> getOneToManies(Class<? extends Type> clazz){
		return classO2Ms.get(clazz);
	}
	public static Map<Field, ManyToManyMapping> getManyToManies(Class<? extends Type> clazz){
		return classM2Ms.get(clazz);
	}
	public static Map<String, ManyToManyMapping> getAllManyToManies(){
		return manyToManies;
	}
	public static ManyToManyMapping getManyToManyMapping(String name){
		return manyToManies.get(name);
	}

}
